#pragma once

#include "sock.hpp"
#include <sys/select.h>

#define BACK_LOG 5
#define NUM (sizeof(fd_set)*8)
#define DFL_FD -1

namespace ns_select{

    class SelectServer{
        private:
            int listen_sock;
            unsigned short port;
        public:
            SelectServer(unsigned short _port):port(_port)
            {}

            void InitSelectServer()
            {
                listen_sock = ns_sock::Sock::Socket();
                ns_sock::Sock::Bind(listen_sock, port);
                ns_sock::Sock::Listen(listen_sock, BACK_LOG);
            }

            void Run()
            {
                fd_set rfds;
                int fd_array[NUM] = {0};
                ClearArray(fd_array, NUM, DFL_FD); //用来初始化数据中的所有fd

                fd_array[0] = listen_sock;    //把监听套接字sock写入数组的第一个元素

                for ( ; ; ){
                    //时间也是输入输出，所以如果你是间隔性的timeout返回，那么就需要对时间也进行重新设定
                    struct timeval timeout = {5, 0}; //每隔 5s timeout一次
                    //对所有的合法fd进行在select中重新设定
                    int max_fd = DFL_FD;
                    FD_ZERO(&rfds); //清空所有的read fd
                    //第一次循环的时候，我们fd_array数组中至少已经有了一个fd，listen_sock
                    for(auto i = 0; i < NUM; i++){
                        if(fd_array[i] == DFL_FD){
                            continue;
                        }
                        //说明是需要添加的合法fd
                        FD_SET(fd_array[i], &rfds);
                        if(max_fd < fd_array[i]){
                            max_fd = fd_array[i]; //更新最大文件描述符
                        }
                    }
                    //1. select 阻塞等待
                    //2. timeval={0}; 非阻塞轮询
                    //3. 阻塞+轮询,timeval = {5,0}; 5s之内，阻塞等待，5s之后，select返回，无论是否有事件就绪
                    switch(select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr)){
                        case 0:
                            std::cout << "timeout: " << timeout.tv_sec << std::endl;
                            break;
                        case -1:
                            std::cerr << "select error" << std::endl;
                            break;
                        default:
                            //正常的事件处理
                            //std::cout << "有事件发生... timeout: " << timeout.tv_sec <<std::endl; 
                            HandlerEvent(rfds, fd_array, NUM);
                            break;
                    }//end swith
                }//end for
            }
            void HandlerEvent(const fd_set &rfds, int fd_array[], int num)
            {
                //如何判定那些文件描述符就绪了呢？只需要判定特定的fd是否在rfds集合中
                //我都有那些文件描述符呢？？fd_array[]
                for(auto i = 0; i < num; i++){
                    //过滤掉不需要的fd
                    if(fd_array[i] == DFL_FD){
                        continue;
                    }

                    //是一个合法的fd，但是不一定就绪了
                    if( fd_array[i] == listen_sock && FD_ISSET(fd_array[i], &rfds)){
                        //是一个合法的fd，并且已经就绪了,是连接事件到来
                        //accept
                        struct sockaddr_in peer;
                        socklen_t len = sizeof(peer);
                        //在这里的时候会不会block阻塞呢？？不会!
                        int sock = accept(fd_array[i], (struct sockaddr*)&peer, &len);
                        if(sock < 0){
                            std::cerr << "accept error" << std::endl;
                            continue;
                        }
                        uint16_t peer_port = htons(peer.sin_port);
                        std::string peer_ip = inet_ntoa(peer.sin_addr);

                        std::cout << "get a new link: " << peer_ip << ":" << peer_port << std::endl;
                        //是不是可以进行对应的recv？？不可以！recv是IO，等 + 拷贝, select 知道！！！
                        //而是将改文件描述符添加到fd_array数组中！ 为什么？
                        if(!AddFdToArray(fd_array, num, sock)){
                            close(sock);
                            std::cout << "select server is full, close fd : " << sock << std::endl;
                        }
                    }
                    else{
                        //hander normal fd
                        if(FD_ISSET(fd_array[i], &rfds)){
                            //是一个合法的fd，并且已经就绪了,是读数据事件就绪
                            //实现读写,会阻塞吗？？绝对不会
                            char buffer[1024];
                            //你能确定你读完了请求吗？？？
                            //如果我一条链接给你发了多个请求数据，但是每个都只有10字节, 粘包？
                            //如果没有读到一个完整的报文，数据可能丢失
                            //这里我们怎么保证自己能拿到完整的数据呢？？
                            //1. 定制协议
                            //2. 还要给每一个sock定义对应的缓冲区
                            ssize_t s = recv(fd_array[i], buffer, sizeof(buffer)-1, 0); //bug!
                            if(s > 0){
                                buffer[s] = 0;
                                std::cout << "echo# " << buffer << std::endl;
                            }
                            else if(s == 0){
                                std::cout << "client quit" << std::endl;
                                close(fd_array[i]);
                                fd_array[i] = DFL_FD; //清除数组中的文件描述符
                            }
                            else{
                                std::cerr << "recv error" << std::endl;
                                close(fd_array[i]);
                                fd_array[i] = DFL_FD; //清除数组中的文件描述符
                            }
                        }
                        else{
                            //TODO
                        }
                    }
                }
            }
            ~SelectServer(){}
        private:
            void ClearArray(int fd_array[], int num, int default_fd) //用来初始化数据中的所有fd
            {
                for(auto i = 0; i < num; i++){
                    fd_array[i] = default_fd;
                }
            }
            bool AddFdToArray(int fd_array[], int num, int sock)
            {
                for(int i = 0; i < num; i++){
                    if(fd_array[i] == DFL_FD){ //说明该位置没有被使用
                        fd_array[i] = sock;
                        return true;
                    }
                }
                //说明我们的数组内空间被使用完毕了
                return false;
            }

    };
}

